Swift之访问控制(Access Control)

您所在的位置:网站首页 swift 开发 Swift之访问控制(Access Control)

Swift之访问控制(Access Control)

#Swift之访问控制(Access Control)| 来源: 网络整理| 查看: 265

前言

目前,Swift 语言已经更新到了 5.x 版本,记得 18 年 3 月份新产品立项,决定使用 Swift 开发项目时,还是 Swift 4.x。在此期间,一直使用 CocoaPods 作为第三方库管理工具,尽管 Swift 和 Xcode 已然经过几多版本更迭,笔者 pod update 的次数却屈指可数。很长一段时间里,Xcode 的 Issue navigator 充斥着各种警告⚠️,其中 Swift Compiler Warning 尤为瞩目。编译器警告数量众多,以下几种是其主要组成部分:

'***' was never used, consider replacing with '_' or removing it Variable '***' was never mutated, consider changing to 'let' constant 'public' modifier is redundant for property declared in a public extension

本着“不要忽视编译器警告”的准则,最近更新了仓库,警告也随之消失。而最后一条引发的对访问控制使用规范的疑惑,便是撰写本文的诱因。

一个例子

以 Kingfisher 的 KingfisherCompatible 协议为例,4.10.1 版本中相关代码如图:

警告表明,为一个声明在以 public 修饰的 extension 中的属性使用 public 修饰符是多余的。读起来有点绕,换句话说:一个以 public 修饰的 extension,它的属性无需再用 public 修饰。更新之后 5.15.8 版本相关代码如图:

可以看到,新版本中去掉了 extension KingfisherCompatible 的 public 修饰符,保留了 kf 的。而 4.10.1 版本的代码建议是移除 kf 的修饰符,两者有何区别?带着疑问,我们来看官方文档中关于 protocol 和 extension 访问控制使用规范的描述。

Description For Protocols:

在定义阶段为协议显式声明访问级别,可使协议只能在特定访问上下文中使用。协议定义时,其内部成员的访问级别默认与协议相同,如定义一个 public protocol,其内部成员默认访问级别也是 public。

使用 public 修饰的其他类型,隐式为其内部成员提供了默认访问级别 internal。

For Extensions:

定义的 class、structure 或 enumeration 类型,可以在任何能访问的地方,使用 extension 对其扩展。扩展内新增成员拥有和原始类型中声明的成员相同的访问级别。例如,扩展一个 public 或 internal 修饰的类型,扩展内新增成员的默认访问级别是 internal;fileprivate 是 fileprivate;private 是 private。

此时,modifier 访问级别默认与 extension Access 相同,即为 internal,只能在 Module AccessControl 中访问。

或者,为一个 extension 显式声明修饰符,其成员就默认拥有了新的访问级别,扩展内成员仍可以显式声明访问,来覆盖默认访问级别。

为 modifier 显式声明 public,覆盖了默认的 internal,报错消失。

不能为一个遵循某协议的扩展显式声明访问级别,因为扩展内的协议实现,已由该协议本身提供了默认访问级别。

读完文档,我们试着解答上面示例的问题,移除 extension KingfisherCompatible 的 public 修饰符和移除 kf 的,两种方式有何区别。

5.15.8 版本。

图中 extension KingfisherCompatible 未显式声明访问级别,默认为 internal,所以 kf 默认访问级别也为 internal,此时 kf 只能在当前模块(Module)中使用,外部不可访问。但代码中为 kf 显式声明 public,覆盖了原有的 internal,这样一来,开发者就能访问 kf,使用 KingfisherCompatibleInstance.kf.setImage 来展示图片了。

4.10.1 版本。

代码建议,移除 kf 的 public 修饰符。由于 extension KingfisherCompatible 显式声明了 public,所以 kf 默认访问级别也是 public,此时已是对外可访问状态,再为 kf 显式声明 public 确实 redundant。

测试:

可以看到,两种方式的最终效果一致。既然如此,我们开发中应选择哪一种作为标准?

我认为应该选择,为 extension 内成员显式声明访问级别的方式。这一点在系统库 API 中有很多体现,比如 UIView:

这种方式有一个好处,清晰明了。 当查阅 API 时,它的适用版本、调用对象和访问级别等一并声明在头部,使我们可以毫不费力就了解它有哪些使用条件。

试想一下,如果某个 API 没有显式声明访问级别,它的注释长度以屏幕为单位,我们甚至不知道它是声明在定义(definition)中还是扩展(extension)中,而它的访问级别又特别重要,我们就不得不伸出食指按在鼠标滚轮上反复滑动,急促而均匀的哒~哒声,时刻在提醒“前途未卜”。当然,如果你是触控板用户,两指滑动的确能省些力气,但我敢肯定,这种 API 对于那些左手指月按下触控板的同时,右手指谨慎拖拽滚动条的开发者来说,是极不友好的。

我相信,无论是有何种操作习惯的开发者,都希望查阅 API 的方式简便而快捷。

以上是仅针对 KingfisherCompatible 协议访问控制使用情况的解析,下面我们了解一下访问控制使用规范的更多细节。

访问控制

访问控制,通过限制你的代码和其他源文件(Source files)及模块(Modules)之间的访问,以达到隐藏代码实现细节,和指定首选可用接口的目的。

Swift 会为多种场景提供默认访问级别,如果你的 app 是 single-target,就没有显式声明访问级别的必要。

访问控制模型是基于模块(Modules)和源文件(Source files)的概念实现的。

模块

模块是一个独立的代码分发单元,可以由另一个模块使用 import 关键字导入。一个 framework或一个作为独立单元交付的应用程序,都称为模块。

源文件

源文件是一个模块中的源代码文件。

简洁起见,代码中所有可使用访问控制的部分,如属性、类型及函数等,以下统称为 实体。

访问级别 五种级别

Swift 提供了五种不同的访问级别,分别是:open、public、internal、fileprivate 和 private,访问权限依次由高到低。

open open 修饰的实体,在本模块的所有源文件和被引入到的其他模块的任意源文件中,都是可访问的。open 只能修饰类和类成员,表示可被子类化和重写。

public public 的作用和 open 相似,修饰的实体,在本模块的所有源文件和被引入到的其他模块的任意源文件中,都是可访问的。唯一的不同是,public 修饰的实体不可被子类化或重写。如:

internal internal 修饰的实体,仅能在本模块中访问。若无显式声明,代码中所有实体的默认访问级别即为 internal。所以,当你在开发一款 single-target app,若没有对外可见的必要,就无需为实体显式声明访问级别。除非在特定场景下,你有意对本模块中其他代码隐藏代码细节,可使用 fileprivate 或 private 实现功能。

比如多人协作开发,你要在自己写的文件中定义了一个或多个类型,又担心可能会与其他人定义的类型重名,就可以这样:

当然,我们日常开发过程中,应尽量避免重名的情况发生,可一旦有必要,也不妨用一用。

fileprivate fileprivate 修饰的实体,仅在当前文件内可访问。如:

private private 修饰的实体,仅在当前封闭声明中和与该声明同一文件的扩展中可访问。如:

使用总则

不能用一个更低访问级别的实体来定义当前实体。如:

以 internal、fileprivate 或 private 修饰的类型,不能定义一个 public 变量,因为在 public 变量使用的地方,这个类型均不可用。

函数的访问级别不能高于其参数类型和返回值类型,因为这种函数的使用场景,其组成部分的类型不可用。如:

默认访问级别

若无显式声明,代码中所有实体的访问级别默认为 internal。如果定义一个 public 或 internal 类型,其成员的访问级别默认为 internal;如果定义一个 fileprivate 或 private 类型,其成员的访问级别默认为 fileprivate 或 private。

元组类型

元组类型的访问级别,与其所有组成类型中访问级别最低者相同。

枚举类型

枚举类型的所有 case 拥有和该类型相同的访问级别,无法为单个 case 显式指定访问级别。另外,有原始值或关联值的枚举类型,其原始值或关联值类型的访问级别应不低于枚举类型。

嵌套类型

嵌套类型的访问级别与其包含类型相同,包含类型是 public 的情况除外。 在 public 类型中定义的嵌套类型自动拥有 internal 访问级别,可以为嵌套类型显式声明 public,使其对外可用,。

子类化

在当前访问上下文中的类,当前模块中的其他类,以及不同模块中以 open 修饰的类,都可以被子类化。子类的访问级别不高于父类。

如果和被子类化的类位于同一模块,则可以重写任何在当前访问上下文中可见的类成员(方法、属性、初始化器及下标等);如果不同模块,则可以重写任何以 open 修饰的类成员。

重写可赋予父类成员更高的访问级别,甚至,在父类成员可访问的范围内,即使父类成员的访问级别比子类成员低,子类也可以调用父类成员。

常量、变量、属性及下标

常量、变量和属性的访问级别不高于所属类型,下标的访问级别不高于其下标类型或返回值类型。

getter 和 setter

常量、变量、属性及下标的 getter 和 setter 的访问级别自动与其同步。你可以赋予 setter 一个比 getter 更低的访问级别来限制可读写范围,如 internal(set)、fileprivate(set) 或 private(set)。在某些特定场景下,你甚至需要使用两个访问级别来分别控制 setter 和 getter 的使用范围。

我们可以从第三方库源码中找到一些使用范例。如 Kingfisher:

public 保证 getter 对外可见,private(set) 保证 setter 对外不可见。所以当外部模块使用 KFCrossPlatformImageViewInstance.kf.taskIdentifier = someValue 时,会发现 setter 是不可访问的。

初始化器

自定义初始化器的访问级别,可以高于其所属类型。而 required 初始化器的访问级别,必须与所属类型相同。与函数参数和方法参数类似,初始化器参数类型的访问级别,不高于初始化器本身。

默认初始化器

Swift 为那些其所有属性已有默认值但自身没有初始化器的结构体或基类,自动提供一个无参的初始化器,该初始化器的访问级别,默认与所属类型相同。如果该类型以 public 修饰,则默认初始化器的访问级别为 internal,可以为默认初始化器显式声明 public,使其对外可见。

结构体类型默认成员初始化器

如果一个结构体类型的任一存储属性,其访问级别为 fileprivate 或 private,则默认初始化器的访问级别,即被视为 fileprivate 或 private。

两图对比,age 被 private 修饰之后,Person 类型的成员初始化器转为不可见。

协议 继承

子协议的访问级别不高于父协议。

遵守

一个类型可以遵守比自己访问级别低的协议。一个类型遵守特定协议的上下文环境,是该类型和协议两者的访问级别中较低者。比如,一个 public 类型遵守一个 internal 协议,遵守的访问级别也是 internal。

一个类型遵守协议时,要保证协议需求的类型实现,访问级别不低于协议遵守。

Internal 遵守 Access 协议的访问级别为 internal,所以 modifier 的访问级别应不低于 internal。

扩展 扩展中的私有成员

同一文件中,对类、结构体或枚举的扩展,其内容可被视作原始类型声明的一部分。于是:

在原始类型中声明的 private 成员,在同一文件的扩展中可使用; 在扩展中声明的 private 成员,在同一文件的另一扩展中可使用; 在扩展中声明的 private 成员,在同一文件的原始类型中可使用。

这个特性使 extension 具有了组织代码结构的功能。

我们知道,在系统库或者第三方开源库中,开发者都利用了这一特性为代码分区,让具有相同或相似功能的代码聚合,使代码结构更清晰。日常开发工作时,尤其多人协作开发,为了更快速地创建文件,和保证代码风格统一,我们会自定义一些常用的文件模板,模板可能是这样的:

随着业务逻辑增加,文件会有逐渐 Massive 的倾向,查找业务逻辑变得艰难。如果能尽早利用 extension 为代码分区并做适当的注释,我们就可以使用 Jump Bar 轻松定位目标代码。

泛型

泛型类型或泛型函数的访问级别,是其自身访问级别与其参数类型的访问级别的最小值。

类型别名

为实现访问控制,你定义的任何类型别名,都被视为与原类型不同的类型。类型别名的访问级别不高于原类型。规则同样适用于用于满足协议遵守的关联类型的类型别名。

上图中,Phone 类型别名的访问级别 public 高于 Object 类型的 internal,报错;Wine 类型别名的访问级别 private 低于 Object 类型的 internal,正常;Winery 遵守 Factory 协议,关联类型别名 Product 的访问级别 internal 高于 Wine 类型别名的 private,报错。

最后

以上内容的介绍顺序,基本与 The Swift Programming Language Guide(以下简称 Guide) 中 Access Control 部分一致,内容多以 翻译 + 示例 的形式呈现。

电脑升级 Big Sur 以后,Safari 自带了翻译功能。笔者一开始想节省时间,就试用了 Safari 翻译,但仅就 Guide 翻译结果而言,笔者并没有感受到 Safari 翻译带来的便利,译文中偶然但不意外出现的生硬翻译,反而加大了理解的难度。相同一段内容,翻译造成的阅读障碍,远比英文原版严重。鉴于如此不良好的使用体验,笔者最终选择自己翻译,有疑惑的概念则更多地参考了 Google Translate 和 Google Search。

示例部分,笔者力求使用简短的代码完整表达文档内容。整体 review 的过程中,又发现了报错信息未显示完全、命名拼写有误或缺少重要注释等诸多问题,经过多次修改之后,整篇文章才算完成。

本文撰写主要基于笔者对 Guide 中 Access Control 部分的个人理解,示例代码片段节选自 Kingfisher 及笔者测试所写。笔者深知自己翻译和编码水平有限,如文中有理解偏颇或表述不精准之处,还请直言解惑。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3